接下來的篇章我們會把目光聚焦於 interfaces/ 這個目錄底下的內容,想確認 slate package 完整的 src directory 的讀者們可以回到 上一篇 做確認。
我們首先來為它底下的 files 做點簡單的分類與介紹:
除了 custom-types.ts 是專門定義與處理 type extension 之外,其他的 file 都各自代表著一組 concept ,也就是上圖畫了紅線的部分。它們各自同時擁有:定義這組 concept 相關的 types 、這組 concept 提供給開發者使用的 method apis。
我們用一個名叫 Example
的範例來講解 concept file 裡基本的 code-structure :
export type ExampleType = {
// type declaration, use "interface" to declare it if it's extendable
}
export interface ExampleInterface {
// Concept method apis interface declaration
}
export const Example: ExampleInterface = {
// Concept methods implementation
}
如果 type 被定義為 extendable 的話,我們則能看到額外引用 custom-types.ts 裡的 ExtendedType
type utility 去擴充其基本 BaseType 的 type 。
type 來 type 去的是想亂死誰,直接上個範例最清楚,一樣用 Example
來介紹。
import { ExtendedType } from './custom-types';
export interface BaseExample {
// type declaration
}
export type Example = ExtendedType<'Example', BaseExmaple>
要注意其實上面的範例放進 ExtendedType
是不過關的,因為 ExtendedType
吃得第一個 string generic 是有用一個 Union type 限制住的,詳情我們後續在 custom-type 的篇章會再深入講解,目前只是先讓讀者們有個概念而已。
接著我們看到有被黃框的 files ,這些是我們在建立 editor 與操作 editor value 時主要會使用到的概念,我們接下來會花不少的篇幅,拿官方提供的註解當溫開水配著服用,依序介紹裡面的 types ,而各個 files 裡提供的 method apis 因為數量眾多又雜又亂,一條一條介紹完另一個 30 天就又過去了所以我們就不在這邊細說,筆者的時間跟心態都算充裕的話再另外整理成 reference 提供給各位吧~
那麼就如標題所言,我們首先會針對 slate 的 document model 所需要用到的 concepts 做介紹,分別是: text 、 element。
這裡面就只有一個 Extendable 的 Text type 而已,光看名字應該也能很直覺的反應出它代表的就是 editor 裡的『文字資料』。
/**
* `Text` objects represent the nodes that contain the actual text content of a
* Slate document along with any formatting properties. They are always leaf
* nodes in the document tree as they cannot contain any children.
*/
export interface BaseText {
text: string
}
export type Text = ExtendedType<'Text', BaseText>
Text
type 只規定了其中的內容物必須要有一個類別為 string 的 text
property ,開發者可以依照自己的喜好定義其他的 properties ,就拿我們在 Day3 舉過官方範例來看看。
下圖只要有被紅圈圈到的 Object 都各為一組 Text
再來看看它的 module declaration
type CustomText = {
bold?: boolean
italic?: boolean
code?: boolean
text: string
}
// slate module "Text" declaration
declare module 'slate' {
interface CustomTypes {
Text: CustomText
}
}
把定義好的 module 引入就能使用自定義的 CustomText 隨意開發了,就是這麼輕鬆寫意。
Text 提供的 method apis 裡有一個特別值得提到的 method 是 decorations()
/**
* Get the leaves for a text node given decorations.
*/
decorations(node: Text, decorations: Range[]): Text[] {
... // method implementation
}
Range
的概念我們會在 下一篇 介紹,目前讀者先知道它是 Slate 中描述一段可以橫跨不同的 Text
甚至 Element
的『字元集範圍』就好,一個 Range 裡面會同時有 anchor 與 focus 這兩個 properties 代表這個 range 的起始字元位置以及結束字元位置。
當我們丟給 decorations 合法的 Text 以及 range list ,也就是這些 Ranges 的範圍是正確的位於該 Text 內時,它會動態地幫我們依照這些 Range 拆成一組 Text list。
const text = { text: 'This is a text example.' };
const ranges = [{
anchor: { path: [0, 0], offset: 5 },
focus: { path: [0, 0], offset: 7 },
}];
/**
returns: [
{ text: 'This ' },
{ text: 'is' },
{ text: ' a text example.' },
] */
Text.decorations(text, ranges);
當然你也可以放上自定義的 Text property
const text = { text: 'This is text example2.' };
const ranges = [{
anchor: { path: [0, 0], offset: 5 },
focus: { path: [0, 0], offset: 7 },
bold: true,
}];
/**
returns: [
{ text: 'This ' },
{ text: 'is', bold: true },
{ text: ' text example2.' },
] */
Text.decorations(text, ranges);
嗯?幹嘛多此一舉?直接修改 document model 裡的 value 不就好了嗎?
筆者一開始也很困惑於這個功能存在的意義,確實直接修改 value 就能達到一模一樣的效果了,後來在官方提供的 Search Highlighting example 裡找到了答案:
它很適合用於開發 「修改的頻率高,幅度大,卻只修正 Text properties 而不會更改到文字的內容或其他 value 的功能」。正如其名 decorations ,它的工用就像是在整個工程的尾端輕輕的裝飾文字的屬性,因為不會去更動到 editor value ,所以可以省略掉許多更新整個 value tree 龐雜的運算,因此把它放在 view layer 的淺層,動態地計算 decorate 後的 Text ,會讓速度整個快上非常非常多。(讀者可以自己去實驗看看 XD
裡面主要定義的 type 也只有一個 Extendable 的 Element
type 而已,slate 的 document model 就是由 Element
與 Text
這兩種 types 所組成的,相對於 Text
所代表的文字資料, Element
代表 editor 裡的元件資料
/**
* `Element` objects are a type of node in a Slate document that contain other
* element nodes or text nodes. They can be either "blocks" or "inlines"
* depending on the Slate editor's configuration.
*/
export interface BaseElement {
children: Descendant[]
}
export type Element = ExtendedType<'Element', BaseElement>
Element
type 只規定了其中的內容物必須要有一個類別為 children 的 Descendant[]
property ,開發者一樣可以依照自己的喜好定義其他的 properties 。
Descendant
type 象徵著 slate document tree 裡的子節點,我們會在之後的 node concept 章節 詳細介紹,我們現在先偷瞄一眼 Descendant
type 的定義
/**
* The `Descendant` union type represents nodes that are descendants in the
* tree. It is returned as a convenience in certain cases to narrow a value
* further than the more generic `Node` union.
*/
export type Descendant = Element | Text
可以看到 Element
與 Descendant
這兩個 type 是彼此關聯的,在 Element
的 children
property 裡再放上另一個 Element
也是沒問題的,這也是為什麼開發者可以很直觀地透過 slate 建立擁有巢狀結構的 feature ,只要照著 type 的規範走 slate 完全不在乎你的 document tree 長什麼樣子,你愛怎麼巢就怎麼巢。
另外我們也可以在 Element
type 的註解裡看到,開發者也能針對各個不同的 elements 定義 blocks
或 inlines
屬性,這部分因為牽涉到 slate editor 的 config 定義,我們就放到後面介紹 editor concept 的篇章吧!
element.ts 裡還有一個附加的 type 是 ElementEntry
,它主要是用在進行 iterate 相關操作的用途上,關於 iterate 我們之後會在整理出一篇文章跟各位分享分享。
哇今天的內容不少誒!來稍微整理一下:我們首先對 interfaces/ 這個目錄底下的 files 做了個簡單的分類 → 定義畫了 紅線 的 concept files 裡的 code structure → 分別介紹了 Document model 裡會出現的
Text
type 以及decorations
methods 、Element
type 以及它與Descendant
type 的關係
感謝你的整理,今天的篇幅有那麼一點長,請讀者花些時間消化一下,緊接著到下一篇我們要介紹 slate 是如何實現 document value 的『定位』這件事的。
咱們一樣下一篇見啦~